#include "Metagraphics.h"
#include "PlainLineRenderer.h"
#include "PlainPolyRenderer.h"
#include "CenteredBlendedBoxRenderer.h"
#include "GuiElementRenderer.h"
#include <iostream>
#include "../core/Logger.h"
#include "SDL.h"

namespace k2graphics
{

// bizarro static initialization
Metagraphics* Metagraphics::_theGraphics = NULL;

Metagraphics::Metagraphics()
{
	LOG(logDEBUG) << "Metagraphics init.";

	_gInternals = new GraphicsInternals();

	// NULL out render objects and indicate that all slots are free
	for(int i = 0; i < GRAPHICS_TABLE_SIZE; ++i)
	{
		_renderObjects[i] = NULL;
		_freeObjectSlots.push_back(i);
	}

	// NULL out viewports
	for(int i = 0; i < MAX_VIEWPORTS; ++i)
	{
		_abstractViewports[i] = NULL;
		_concreteViewports[i] = NULL;
		_viewportLinks[i] = NULL;
	}

	// create renderers? (or at least NULL them out)
	for(int i = 0; i < NUM_RENDERERS; ++i)
		_renderers[i] = NULL;

	_renderers[0] = new PlainLineRenderer();
	_renderers[1] = new PlainPolyRenderer();
	_renderers[2] = new CenteredBlendedBoxRenderer();
	_renderers[3] = new GuiElementRenderer;

	resetVideoMode(DEFAULT_SW, DEFAULT_SH, false);

	// load in default text texture
	//GraphicsInternals* gi = _theGraphics->getInternals();
	//std::string tempfn = "font.png";
	//int res = _gInternals->textureDepot.loadRawTextureFromFile(tempfn, 255);
	//LOG(logDEBUG) << "Metagraphics font tex: " << res;
	//_fontTexGLID = _gInternals->textureDepot.getTexture(res)->getRawGLTexture();

	//_conColors = makePrettyColors();

	LOG(logDEBUG) << "Metagraphics init done.";
}

void Metagraphics::addCustomRenderer(Renderer* r, int slot)
{
	_renderers[slot] = r;
}

void Metagraphics::getLuaInterface(lua_State* L)
{
	// this, like all super manager interface calls, returns a BRAND NEW interface
	// (it can't cache its interface, since it would need to have a lua state
	//  to put the interface in, but SuperManagers, by definition, should be accessible
	//  from multiple lua states, so each time it should just make a new one... caching
	//  of SuperManager interfaces should be implemented at the sheet level
	// (i.e., the Sheet's LuaLocalManager should query all the SuperManagers for interfaces
	//  when it is created)

	// create new table
	//lua_newtable(L);

	// create the array of functions we want to dump
	static const struct luaL_Reg mylib [] = {
	  {"SetVideoMode", li_setVideoMode},
      {"SetLayerParallax", setLayerParallax},
	  {"LoadTexture", loadTexture},
	  {"CreateSubTexture", createSubTexture},
	  {"CreateConcreteViewport", createConcreteViewportLua},
	  {"CreateAbstractViewport", createAbstractViewportLua},
	  {"LinkViewports", linkViewportsLua},
	  {"SetConcreteViewportPosition", setConcreteViewportPosition},
	  {"SetAbstractViewportPosition", setAbstractViewportPosition},
	  {"CreateRenderObject", createRenderObjectLua},
	  {"DestroyRenderObject", destroyRenderObjectLua},
	  {"SetROPosition", setROPosition},
	  {"SetRODims", setRODims},
	  {"SetROLayer", setROLayer},
	  {"SetROByte", setROByte},
	  {"SetROInt", setROInt},
	  {"SetROFloat", setROFloat},
	  {"SetROGroup", setROGroup},
	  {"SetROBits", setROBits},
      {NULL, NULL}  /* sentinel */
    };

	// dump them into said table
	luaL_register(L, NULL, mylib);

	// return said table
	return;
}

void Metagraphics::update(double dt)
{
	// woo! do OpenGL update stuff here
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	sortObjectsIntoViewports();
	updateGraphics(dt);

	SDL_GL_SwapBuffers();
}

int Metagraphics::resetVideoMode(int screenWidth, int screenHeight, bool fullscreen)
{
	// SDL/OpenGL initialization (doublebuffered, depthbuffered, 32 bit color)
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);

	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);

	SDL_Surface* drawContext;
	Uint32 flags;

	if(fullscreen)
		flags = SDL_OPENGL | SDL_HWSURFACE | SDL_FULLSCREEN;
	else
		flags = SDL_OPENGL | SDL_HWSURFACE;

	drawContext = SDL_SetVideoMode(screenWidth, screenHeight, 0, flags);

	_gInternals->screenWidth = screenWidth;
	_gInternals->screenHeight = screenHeight;

	return 1;
}

// SetLayerParallax(avID, layer, invparallax)
int Metagraphics::setLayerParallax(lua_State* L)
{
	int avid = (int)lua_tointeger(L, 1);
	int layer = (int)lua_tointeger(L, 2);
	double invp = (double)lua_tonumber(L, 3);

	AbstractViewport* av = _theGraphics->getAbstractViewport(avid);
	
	if(av)
	{
		RenderLayer* rl = av->getLayer(layer);
		if(rl)
		{
			rl->setParallax(invp);
		}
		else
		{
			LOG(logERROR) << "Invalid layer!\n";
		}
	}
	else
	{
		LOG(logERROR) << "Invalid abstract viewport ID: " << avid << std::endl;
	}

	return 0;
}

// int LoadTexture(filename, slot)
int Metagraphics::loadTexture(lua_State* L)
{
	GraphicsInternals* gi = _theGraphics->getInternals();

	std::string fn = lua_tostring(L, 1);
	int slot = (int)lua_tointeger(L, 2);

	int res = gi->textureDepot.loadRawTextureFromFile(fn, slot);
	lua_pushinteger(L, res);

	return 1;
}

int Metagraphics::li_setVideoMode(lua_State* L)
{
	int w = (int)lua_tointeger(L, 1);
	int h = (int)lua_tointeger(L, 2);
	bool fullscreen = lua_tonumber(L, 3) > 0 ;
	_theGraphics->resetVideoMode(w, h, fullscreen);
	return 0;
}

// int CreateSubTexture(srcTexture, slot, x, y, w, h)
int Metagraphics::createSubTexture(lua_State* L)
{
	GraphicsInternals* gi = _theGraphics->getInternals();
	int srcTex = (int)lua_tointeger(L, 1);
	int destSlot = (int)lua_tointeger(L, 2);
	float x = (float)lua_tonumber(L, 3);
	float y = (float)lua_tonumber(L, 4);
	float w = (float)lua_tonumber(L, 5);
	float h = (float)lua_tonumber(L, 6);

	int res = gi->textureDepot.createSubTexture(srcTex, destSlot, x, y, w, h);
	lua_pushinteger(L, res);

	return 1;
}

// int CreateConcreteViewport()
int Metagraphics::createConcreteViewportLua(lua_State* L)
{
	int vp = _theGraphics->createConcreteViewport();
	lua_pushinteger(L, vp);
	return 1;
}

// int CreateAbstractViewport()
int Metagraphics::createAbstractViewportLua(lua_State* L)
{
	int vp = _theGraphics->createAbstractViewport();
	lua_pushinteger(L, vp);
	return 1;
}

// LinkViewports(abstractVpID, concreteVpID)
int Metagraphics::linkViewportsLua(lua_State* L)
{
	int vp1 = (int)lua_tointeger(L, 1);
	int vp2 = (int)lua_tointeger(L, 2);
	_theGraphics->createLocalViewportLink(vp1, vp2);
	return 0;
}

// SetConcreteViewportPosition(vpID, x, y, w, h)
int Metagraphics::setConcreteViewportPosition(lua_State* L)
{
	int cvpid = (int)lua_tointeger(L, 1);
	float x = (float)lua_tonumber(L, 2);
	float y = (float)lua_tonumber(L, 3);
	float w = (float)lua_tonumber(L, 4);
	float h = (float)lua_tonumber(L, 5);
	
	ConcreteViewport* cv = _theGraphics->getConcreteViewport(cvpid);
	
	if(cv)
	{
		cv->setPosition(x, y);
		if(w > 0 && h > 0)
			cv->setDimensions(w, h);
	}
	else
	{
		LOG(logERROR) << "Invalid concrete viewport ID: " << cvpid << std::endl;
	}
	return 0;
}

// SetAbstractViewportPosition(vpID, x, y, w, h)
int Metagraphics::setAbstractViewportPosition(lua_State* L)
{
	int avpid = (int)lua_tointeger(L, 1);
	float x = (float)lua_tonumber(L, 2);
	float y = (float)lua_tonumber(L, 3);
	float w = (float)lua_tonumber(L, 4);
	float h = (float)lua_tonumber(L, 5);
	
	AbstractViewport* av = _theGraphics->getAbstractViewport(avpid);
	
	if(av)
	{
		av->setPosition(x, y);
		if(w > 0 && h > 0)
			av->setDimensions(w, h);
	}
	else
	{
		LOG(logERROR) << "Invalid abstract viewport ID: " << avpid << std::endl;
	}
	return 0;
}


// int CreateRenderObject(targetRenderer, maxB, maxI, maxF)
int Metagraphics::createRenderObjectLua(lua_State* L)
{
	int trender = (int)lua_tointeger(L, 1);
	int maxB = (int)lua_tointeger(L, 2);
	int maxI = (int)lua_tointeger(L, 3);
	int maxF = (int)lua_tointeger(L, 4);
	int renderID = -1;

	LOG(logDEBUG3) << "MBIF: " << maxB << " " << maxI << " " << maxF;

	renderID = _theGraphics->createRenderObject(trender, maxB, maxI, maxF);
	LOG(logDEBUG3) << "Created RO " << renderID;

	lua_pushinteger(L, renderID);
	return 1;
}

// DestroyRenderObject(slot)
int Metagraphics::destroyRenderObjectLua(lua_State* L)
{
	int slotID = (int)lua_tointeger(L, 1);

	_theGraphics->destroyRenderObject(slotID);

	return 0;
}

// SetROPosition(slot, x, y, theta)
int Metagraphics::setROPosition(lua_State* L)
{
	int slot = (int)lua_tointeger(L, 1);
	float x = (float)lua_tonumber(L, 2);
	float y = (float)lua_tonumber(L, 3);
	float theta = (float)lua_tonumber(L, 4);

	RenderObject* theObj = _theGraphics->getRenderObject(slot);

	if(theObj)
	{
		LOG(logDEBUG4) << "UL Trying to set position for " << slot << " to " << x << " " << y;
		theObj->setCanonicalPosition(x, y, theta);
	}
	else
		LOG(logERROR) << "No RO to set RO position for!\n";

	return 0;
}

// SetRODims(slot, width, height)
int Metagraphics::setRODims(lua_State* L)
{
	int slot = (int)lua_tointeger(L, 1);
	float w = (float)lua_tonumber(L, 2);
	float h = (float)lua_tonumber(L, 3);

	RenderObject* theObj = _theGraphics->getRenderObject(slot);

	if(theObj)
	{
		theObj->setBoundingBoxDims(w, h);
	}
	else
		LOG(logERROR) << "No object to set RO bounding box for!\n";

	return 0;
}

// SetROLayer(slot, layer)
int Metagraphics::setROLayer(lua_State* L)
{
	int slot = (int)lua_tointeger(L, 1);
	int layer = (int)lua_tointeger(L, 2);

	RenderObject* theObj = _theGraphics->getRenderObject(slot);

	if(theObj)
	{
		theObj->layer = layer;
	}
	else
		LOG(logERROR) << "No object to set RO layer for!\n";

	return 0;
}

// SetROByte(slot, bindex, b)
int Metagraphics::setROByte(lua_State* L)
{
	int slot = (int)lua_tointeger(L, 1);
	int bindex = (int)lua_tointeger(L, 2);
	int b = (int)lua_tointeger(L, 3);

	RenderObject* theObj = _theGraphics->getRenderObject(slot);

	if(theObj && theObj->rd)
	{
		theObj->rd->setByte(bindex, (unsigned char)b);
	}
	else
		LOG(logERROR) << "No RO to set RO byte for!\n";

	return 0;
}

// SetROInt(slot, iindex, i)
int Metagraphics::setROInt(lua_State* L)
{
	int slot = (int)lua_tointeger(L, 1);
	int iindex = (int)lua_tointeger(L, 2);
	int i = (int)lua_tointeger(L, 3);

	RenderObject* theObj = _theGraphics->getRenderObject(slot);

	if(theObj && theObj->rd)
	{
		theObj->rd->setInt(iindex, i);
	}
	else
		LOG(logERROR) << "No object to set RO int for!\n";

	return 0;
}

// SetROFloat(slot, findex, f)
int Metagraphics::setROFloat(lua_State* L)
{
	int slot = (int)lua_tointeger(L, 1);
	int findex = (int)lua_tointeger(L, 2);
	float f = (float)lua_tonumber(L, 3);

	RenderObject* theObj = _theGraphics->getRenderObject(slot);

	if(theObj && theObj->rd)
	{
		theObj->rd->setFloat(findex, f);
	}
	else
		LOG(logERROR) << "No object to set RO float for!\n";

	return 0;
}

// SetROGroup(slot, g)
int Metagraphics::setROGroup(lua_State* L)
{
	int slot = (int)lua_tointeger(L, 1);
	int gr = (int)lua_tointeger(L, 2);

	RenderObject* theObj = _theGraphics->getRenderObject(slot);

	if(theObj)
	{
		theObj->viewportGroup = gr;
	}
	else
		LOG(logERROR) << "No object to set RO group for!\n";

	return 0;
}

// SetROBits(slot, bits)
int Metagraphics::setROBits(lua_State* L)
{
	int slot = (int)lua_tointeger(L, 1);
	int vpbits = (int)lua_tointeger(L, 2);

	RenderObject* theObj = _theGraphics->getRenderObject(slot);

	if(theObj)
	{
		theObj->viewportBits = vpbits;
	}
	else
		LOG(logERROR) << "No object to set RO bits for!\n";

	return 0;
}

Renderer** Metagraphics::getRenderers()
{
	return _renderers;
}

int Metagraphics::createRenderObject(int renderTarget, int maxB, int maxI, int maxF)
{
	// Make sure there is room
	if(_freeObjectSlots.size() == 0)
		return -1;

	int newID = _freeObjectSlots.front();
	_freeObjectSlots.pop_front();

	_renderObjects[newID] = new RenderObject(renderTarget, maxB, maxI, maxF);
	_liveObjectList.push_back(_renderObjects[newID]);

	_renderObjects[newID]->roID = newID;

	return newID;
}

void Metagraphics::destroyRenderObject(int roID)
{
	// This might be replaced with removeFromLiveList followed by
	// delete local

	RenderObject* target = _renderObjects[roID];

	if(target == NULL)
		return;

	// remove from live objects list
	for(std::vector<RenderObject*>::iterator i = _liveObjectList.begin(); i != _liveObjectList.end(); ++i)
		if( (*i) == target )
		{
			_liveObjectList.erase(i);
			break;
		}

	// delete object
	delete target;

	// null its slot
	_renderObjects[roID] = NULL;

	// add it back to the free list
	_freeObjectSlots.push_back(roID);
}

void Metagraphics::destroyLocalRenderObject(int roID)
{
	RenderObject* target = _renderObjects[roID];

	if(target == NULL)
		return;

	// delete object
	delete target;

	// null its slot
	_renderObjects[roID] = NULL;

	// add it back to the free list
	_freeObjectSlots.push_back(roID);
}

void Metagraphics::removeRenderObjectFromLiveList(int roID)
{
	RenderObject* target = _renderObjects[roID];

	if(target == NULL)
		return;

	// remove from live objects list
	for(std::vector<RenderObject*>::iterator i = _liveObjectList.begin(); i != _liveObjectList.end(); ++i)
		if( (*i) == target )
		{
			_liveObjectList.erase(i);
			break;
		}
}

int Metagraphics::createAbstractViewport()
{
	// search for a free slot
	// this is slightly expensive, but we don't expect to create viewports very often
	for(int i = 0; i < MAX_VIEWPORTS; ++i)
	{
		if(_abstractViewports[i] == NULL)
		{
			_abstractViewports[i] = new AbstractViewport(GRAPHICS_LAYERS);
			return i;
		}
	}

	// no free slot found
	return -1;
}

void Metagraphics::destroyAbstractViewport(int avpID)
{
	if(_abstractViewports[avpID] != NULL)
	{
		delete _abstractViewports[avpID];
		_abstractViewports[avpID] = NULL;
	}
}

int Metagraphics::createConcreteViewport()
{
	// search for a free slot
	// this is slightly expensive, but we don't expect to create viewports very often
	for(int i = 0; i < MAX_VIEWPORTS; ++i)
	{
		if(_concreteViewports[i] == NULL)
		{
			_concreteViewports[i] = new ConcreteViewport();
			return i;
		}
	}

	// no free slot found
	return -1;
}

void Metagraphics::destroyConcreteViewport(int cvpID)
{
	if(_concreteViewports[cvpID] != NULL)
	{
		delete _concreteViewports[cvpID];
		_concreteViewports[cvpID] = NULL;
	}
}

int Metagraphics::createLocalViewportLink(int abstractID, int concreteID)
{
	// search for a free slot
	// this is slightly expensive, but we don't expect to create viewports very often
	for(int i = 0; i < MAX_VIEWPORTS; ++i)
	{
		if(_viewportLinks[i] == NULL)
		{
			if(_abstractViewports[abstractID] != NULL && _concreteViewports[concreteID] != NULL)
			{
				// check to make sure the concrete viewport is not already the destination of some link
				for(int k = 0; k < MAX_VIEWPORTS; ++k)
				{
					if(_viewportLinks[i] != NULL && _viewportLinks[i]->getDest() == _concreteViewports[concreteID])
					{
						std::cout << "Concrete viewport " << concreteID << " is already the target of a link.\n";
						return -1;
					}
				}

				_viewportLinks[i] = new ViewportLink(i);
				_viewportLinks[i]->setLocalDest(_concreteViewports[concreteID], concreteID);
				_viewportLinks[i]->setLocalSrc(_abstractViewports[abstractID], abstractID);
				return i;
			}
			else
			{
				std::cout << "Failure to create viewport link: invalid source or destination ID" << std::endl;
				return -1;
			}
		}
	}

	// no free slot found
	return -1;
}

void Metagraphics::destroyViewportLink(int vplID)
{
	if(_viewportLinks[vplID] != NULL)
	{
		delete _viewportLinks[vplID];
		_viewportLinks[vplID] = NULL;
	}
}

void Metagraphics::sortObjectsIntoViewports()
{
	// sort our objects into the viewports
	//std::cout << "Object sorting...\n";
	for(int i = 0; i < MAX_VIEWPORTS; ++i)
	{
		//std::cout << "i: " << i << std::endl;
		if(_abstractViewports[i] != NULL)
		{
			//std::cout << "clear objects\n";
			// clear all the previous objects...
			_abstractViewports[i]->clearObjects();

			// sort the current objects...
			//std::cout << "Actual object sorting...\n";
			//std::cout << "Number of live objects: " << _liveObjectList.size() << std::endl;
			for(unsigned int j = 0; j < _liveObjectList.size(); ++j)
			{
				//std::cout << "j: " << j << std::endl;
				if(!_liveObjectList[j])
					std::cout << "Null object in live list???\n";
				_abstractViewports[i]->pushObject(_liveObjectList[j]);
			}
		}
	}
}

void Metagraphics::updateGraphics(double dt)
{
	// we should eventually do something in here...
	// (DO OPENGL FRAME SETUP STUFF HERE)

	// do actual viewport rendering here?

	// I'm not sure if this is exactly how I plan on eventually doing it, but for
	// now let's iterate through the links and draw their concrete viewports
	//std::cout << "Viewport rendering...\n";
	for(int i = 0; i < MAX_VIEWPORTS; ++i)
	{
		if(_viewportLinks[i] != NULL && _viewportLinks[i]->getSrc() != NULL && _viewportLinks[i]->getDest() != NULL)
		{
			// update the link
			_viewportLinks[i]->updateLink(dt);

			ConcreteViewport* dest = _viewportLinks[i]->getDest();
			AbstractViewport* src = _viewportLinks[i]->getSrc();
			// then, rendering
			//std::cout << "Rendering viewport " << i << std::endl;
			dest->renderViewport(dt, src, _gInternals);
		}
	}

	// render console if necessary
	/*
	Console* con = UltraManager::getCon();
	if(con->isEnabled())
	{
		//LOG(logDEBUG3) << "Trying to draw console.";
		basicDrawConsole(con, 30, 1.0f, _fontTexGLID, _conColors);
		//LOG(logDEBUG3) << "Done drawing console.";
	}*/
}

}